package pl.szpadel.android.gadu;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.Collection;

import pl.szpadel.android.gadu.packets.BasePacket;
import pl.szpadel.android.gadu.packets.Login80;
import pl.szpadel.android.gadu.packets.ReceivedPacket;
import pl.szpadel.android.gadu.packets.Welcome;

/// Tool responsible for connection with server and performing handshake
public class Connector {
	
	////////////////////////
	// Excetpion classes
	
	/// Base exception class
	@SuppressWarnings("serial")
	public static abstract class ConnectorException extends Exception {
		protected ConnectorException(String msg) {
			super(msg);
		}
	}
	
	/// Exception thrown if there is network error
	@SuppressWarnings("serial")
	public static class NetworkException extends ConnectorException {
		protected NetworkException(String msg) {
			super(msg);
		}
	};
	
	/// Exception thrown when login fails
	@SuppressWarnings("serial")
	public static class LoginException extends ConnectorException {
		protected LoginException(String msg) {
			super(msg);
		}
	};

	/// Exception thrown if there is some irregularity in the protocol
	@SuppressWarnings("serial")
	public static class ProtocolException extends ConnectorException {
		protected ProtocolException(String msg) {
			super(msg);
		}
	};

	/// Exception thrown if server rejects disconnection
	@SuppressWarnings("serial")
	public static class RejectException extends ConnectorException {
		protected RejectException(String msg) {
			super(msg);
		}
	};

	////////////////////////////////////////
	// implementation
	
	/// Parent service
	private CommunicationService mService = null;
	
	/// C-tor - initliazes with service
	public Connector(CommunicationService service) {
		mService = service;
	}
	
	/// Connects to the server, returns connected channel.
	/// Throws exception on failure
	public SocketChannel connect(long uin, String password, Status initialStatus) throws ConnectorException {
		
		// use HTTP service to get server addresses
		Collection<InetSocketAddress> addresses;
		try {
			addresses = HttpServiceConnector.getAddresses(mService, uin);
		} catch( Exception e ) {
			throw new NetworkException("Failed to connect to the HTTP service: " + e.getMessage());
		}
		
		// check the result
		if (addresses.isEmpty()) {
			throw new ProtocolException("Failed to get addresses from HTTP service");
		}
		
		try
		{
			// create and configure channel
			SocketChannel channel = SocketChannel.open();
			channel.configureBlocking(true);
			
			// attempt to connect to one of provided addresses
			for (InetSocketAddress addr : addresses ){
				try {
					AGLog.d(CommunicationService.TAG, "Attempting to connect to  " + addr.toString());
					channel.connect(addr);
					
					AGLog.i(CommunicationService.TAG, "Connected to " + addr.toString());
					break;
				} catch (Exception e) {
					AGLog.w(CommunicationService.TAG, "Failed to connect to " + addr.toString());
				}
			}
			
			// connected?
			if (!channel.isConnected()) {
				throw new ProtocolException("failed to connect to any of the provided addresses");
			}
			
			// wooohoo! start handshake
			ReceivedPacket packet = ReceivedPacket.createFromChannel(channel);
			AGLog.i(CommunicationService.TAG, "Received packet: " + packet);
			
			
			
			// accept only welcome and disconnect packet
			if (packet.getType() == BasePacket.TYPE_DISCONNECT) {
				throw new RejectException("Server rejects the connection");
			}
			if (packet.getType() != BasePacket.TYPE_WELCOME) {
				throw new ProtocolException("unknow welcome packet type: Ox" + Integer.toHexString(packet.getType()));
			}
			Welcome welcome = (Welcome)packet;
			
			// send login packet
			AGLog.i(CommunicationService.TAG, "Sending login packet");
			Login80 login80 = new Login80((int)uin, welcome.seed, password, initialStatus);
			
			login80.toChannel(channel);
			
			// receive response
			AGLog.i(CommunicationService.TAG, "Awaiting response");
			packet = ReceivedPacket.createFromChannel(channel);
			if (packet.getType() == BasePacket.TYPE_LOGIN80_FAILED) {
				AGLog.i(CommunicationService.TAG, "login failed");
				throw new LoginException("login failed");
			} else if (packet.getType() == BasePacket.TYPE_LOGIN80_OK ) {
				AGLog.i(CommunicationService.TAG, "Login OK");
			} else {
				AGLog.e(CommunicationService.TAG, "unknown response type: " + packet.getType());
				throw new ProtocolException("unknown login response type");
			}
			
			return channel;
		} catch (IOException e) {
			throw new NetworkException("Error connecting: " + e.getMessage());
		}
	}

	
}
